/**
 * Copyright 2004 Cordys R&D B.V. 
 * 
 * This file is part of the Cordys FTP Connector. 
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
 package com.eibus.applicationconnector.ftp;

import java.io.File;

import com.eibus.soap.fault.Fault;
import com.eibus.util.logger.CordysLogger;
import com.eibus.util.logger.Severity;
import com.eibus.xml.nom.Document;
import com.eibus.xml.nom.Node;
import com.eibus.xml.xpath.XPath;
import com.eibus.applicationconnector.ftp.localization.message.Messages;
import com.eibus.directory.soap.DirectoryException;
import com.eibus.exception.ExceptionGroup;

/**
 * This class is the handler for FTP's GET command
 */
class DownLoad extends FTPCommand
{
	boolean sync = false;
    private int publishNode;
    private int responseFilesNode;
    private Document document;
    private String FILE_PATH_TAG = "file";
    private static final String STR_FILES ="files";
    private static final String DOWNLOAD_DIRECTORY = "downloads";
    private static final CordysLogger logger = CordysLogger.getCordysLogger(DownLoad.class);

    @Override
	public boolean prepare()
    {
        if (Node.getName(requestXML).equals("multipleDownload"))
        {	
        	FILE_PATH_TAG = "path";
        }
        else
        {
        	FILE_PATH_TAG = "file";
        }
        		
        document   = Node.getDocument(requestXML);
        responseFilesNode = Node.duplicate(Node.getElementNS(requestXML, STR_FILES, getNameSpace()));

        if( responseFilesNode == 0 )
        {
        	logger.log(Severity.ERROR, "<files> node is not available in download request.");
        	Fault fault = response.createSOAPFault(Fault.Codes.CLIENT,Messages.FILES_NOT_SPECIFIED_DOWNLOAD);
        	addFaultDetails2(fault.getDetail(),null,DEFAULT_QNAME,null,null);
        	return false;
        }

        int[] fileNodes = XPath.getMatchingNodes(FILE_PATH_TAG, null, responseFilesNode);
        if( fileNodes.length == 0 )
        {
        	logger.log(Severity.ERROR, "<file> node is not available in download request.");
        	Fault fault = response.createSOAPFault(Fault.Codes.CLIENT,Messages.FILES_NOT_SPECIFIED_DOWNLOAD);
        	addFaultDetails2(fault.getDetail(),null,DEFAULT_QNAME,null,null);
        	return false;
        }

        for (int index = 0; index < fileNodes.length; index ++)
        {
        	int sourceFileNode = XPath.getFirstMatch(".//source", null, fileNodes[index]);
        	String sourceFile = Node.getDataWithDefault(sourceFileNode, "").trim();
            if(sourceFile.length() == 0)
            {
        		Fault fault = response.createSOAPFault(Fault.Codes.CLIENT,Messages.SOURCE_NOT_SPECIFIED);
        		addFaultDetails2(fault.getDetail(),null,DEFAULT_QNAME,null,null);
        		return false;
            }

        	int targetFileNode = XPath.getFirstMatch(".//target", null, fileNodes[index]);
        	String targetFile = Node.getDataWithDefault(targetFileNode, "").trim();
        	
            if(ftpConfiguration.getBasepath() == null && targetFile.length() == 0)
            {
        		Fault fault = response.createSOAPFault(Fault.Codes.CLIENT, Messages.TARGET_FILE_NOT_SPECIFIED);
        		addFaultDetails2(fault.getDetail(),null,DEFAULT_QNAME,null,null);
        		return false;
            }
            
    		if (!ftpConfiguration.getProtocol().equals("SFTP"))
    		{
    			document.createElementWithParentNS("statuscode", "", fileNodes[index]);
    		}
    		
    		document.createElementWithParentNS("status", inProgressMesg, fileNodes[index]);
        }
        
        publishNode = document.createElementNS("download",null,null,getNameSpace(),0);
        addRequestDetailsToResponse(requestXML, publishNode);

        int pFilesNode = document.createTextElement(STR_FILES,null,publishNode);
        Node.appendToChildren(pFilesNode,publishNode);
        return true;
    }

    @Override
	public void commit()
    {
    	try
    	{
	        Thread downloadThread = new Thread(new Downloader(responseFilesNode, Node.duplicate(publishNode)));
	        downloadThread.start();
	
	        int responseXML = response.getXMLNode();
	        sync = Node.getDataWithDefault(Node.getElementNS(requestXML,"synchronous",getNameSpace()),"false").equals("true");
	        if(sync)
	        {
	        	try
	        	{
					downloadThread.join();
				}
	        	catch (InterruptedException e)
				{
	        		logger.log(Severity.ERROR, "An error has occured while downloading " + e.getMessage(), e);
				}
	        }
	        Node.appendToChildren(Node.duplicate(responseFilesNode),responseXML);
    	} finally
    	{
    		Node.delete(publishNode);
    		publishNode = 0;
    		
    		if(sync)
    		{
    			Node.delete(responseFilesNode);
    			responseFilesNode = 0;
    		}
    	}
    }

    private class Downloader implements Runnable
    {
        private int downloaderFilesNode;
        private int downloaderPublishNode;
        
        private String type;
        private String ftpMessage;
        
        FTPException ftpException = null;
        boolean soapFaultOccurred = false;
        
        Downloader(int filesNode, int publishNode)
        {
        	this.downloaderFilesNode = filesNode;
        	this.downloaderPublishNode =publishNode;
        }
        
        @Override
		public void run()
		{
        	try
        	{
	        	int[] fileNodes = XPath.getMatchingNodes(FILE_PATH_TAG, null, downloaderFilesNode);
	        	for (int index = 0; index < fileNodes.length; index ++)
	        	{
	        		int statuscode = Node.getElement(fileNodes[index], "statuscode");
	        		int statusNode = Node.getElement(fileNodes[index], "status");
	            	this.type = Node.getAttribute(fileNodes[index], "type", "ascii");
	            	
	                if(this.type.equals("ascii"))
	                {
	                	adapter.setFileType(IFTPClient.FILE_TYPE.ASCII);
	    			}
	
	                if(this.type.equals("bin"))
	                {
	                	adapter.setFileType(IFTPClient.FILE_TYPE.BINARY);
	    			}
	
	            	String source = Node.getDataWithDefault(XPath.getFirstMatch("source", null, fileNodes[index]), "");
	            	String target = Node.getDataWithDefault(XPath.getFirstMatch("target", null, fileNodes[index]), "");
	            	
	            	source = getProperFileSeparator(source, true);
	            	target = getProperFileSeparator(target, false);
	            	
	            	if (ftpConfiguration.getBasepath() != null && target.length() == 0)
	            	{
	            		target = checkAndCreateDownloadDirectory() + '/' + source.substring(source.lastIndexOf("/")+1);
	            	}
	            	
	            	try
	            	{
	            		ftpMessage = adapter.download(source, target);
	            	} catch (FTPException ftpe)
	            	{
	            		ftpException = ftpe;
	            		soapFaultOccurred = true;
	            	}
	            	
	            	if (soapFaultOccurred)
	            	{
		    			Node.setDataElement(Node.getParent(statusNode), "status", ftpException.getMessage());
		    			Node.appendToChildren(statusNode, fileNodes[index]);
	            	}
	            	else
	            	{
	            		if (ftpConfiguration.getProtocol().equals("SFTP"))
	            		{
	            			Node.setDataElement(Node.getParent(statusNode), "status", ftpMessage);
	            			Node.appendToChildren(statusNode, fileNodes[index]);
	            		}
	            		else
	            		{
	                    	Node.setDataElement(Node.getParent(statuscode), "statuscode", ftpMessage.substring(0, 3));
	                        Node.setDataElement(Node.getParent(statusNode), "status", ftpMessage.substring(4));
	                        Node.appendToChildren(statuscode, fileNodes[index]);
	            			Node.appendToChildren(statusNode, fileNodes[index]);			
	            		}	
	            	}
	        		
	    			int pfilesNode = Node.getElementNS(downloaderPublishNode, STR_FILES, getNameSpace());
	    			Node.appendToChildren(Node.duplicate(fileNodes[index]), pfilesNode);
	    			try
	    			{
						publishFTPMessage(ftpNotificationSubject, Node.duplicate(this.downloaderPublishNode));
					}
	    			catch (ExceptionGroup e)
	    			{
	    				logger.log(Severity.ERROR, e.getMessage(), e);
					}
	    			catch (DirectoryException e)
	    			{
	    				logger.log(Severity.ERROR, e.getMessage(), e);
					}
	    			
	    			soapFaultOccurred = false;
	        	}
	            
	    		if(ftpException != null)
	    		{
	    			adapter.disconnect();
	        		logger.log(Severity.ERROR, "An error has occurred while download. " + ftpException.getMessage());
	                Fault fault = response.createSOAPFault(Fault.Codes.SERVER, Messages.DOWNLOAD_FAILURE, new String[]{ftpException.getLocalizedMessage()});
	                addFaultDetails2(fault.getDetail(), null, DEFAULT_QNAME, null, null);
	                return;
	    		}
	    		
	        	/* After the command is complete, disconnect the adapter from the FTP server */
	        	adapter.disconnect();
        	} finally 
        	{
        		Node.delete(downloaderPublishNode);
        		downloaderPublishNode = 0;
        		
        		if(!sync)
        		{
        			Node.delete(responseFilesNode);
        			responseFilesNode = 0;
        		}
        	}
		}

		private String checkAndCreateDownloadDirectory()
		{
			String downloadDirectory = ftpConfiguration.getBasepath() + '/' + DOWNLOAD_DIRECTORY;
			File file = new File(downloadDirectory);
			if (!file.exists())
			{
				(new File(downloadDirectory)).mkdir();
			}
			
			return downloadDirectory;
		}
    }
}